Day 18: Lambda Expressions and Functional Interfaces
Lambda expressions are a concise syntax for representing anonymous functions. Introduced in Java 8, they enable a functional programming style. They are used together with functional interfaces (interfaces with exactly one abstract method).
Lambda Expression Basic Syntax
Let’s walk through converting anonymous classes to lambdas.
import java.util.Arrays;
import java.util.List;
// Functional interface: exactly one abstract method
@FunctionalInterface
interface Calculator {
int calculate(int a, int b);
}
@FunctionalInterface
interface Printer {
void print(String message);
}
@FunctionalInterface
interface StringProcessor {
String process(String input);
}
public class LambdaBasic {
public static void main(String[] args) {
// 1. Two parameters, expression body
Calculator add = (a, b) -> a + b;
Calculator multiply = (a, b) -> a * b;
Calculator max = (a, b) -> Math.max(a, b);
System.out.println("Addition: " + add.calculate(10, 20));
System.out.println("Multiplication: " + multiply.calculate(5, 6));
System.out.println("Max: " + max.calculate(15, 8));
// 2. Single parameter (parentheses can be omitted)
Printer printer = message -> System.out.println("Output: " + message);
printer.print("Hello!");
// 3. Block body (multiple lines)
Calculator safeDivide = (a, b) -> {
if (b == 0) {
System.out.println("Cannot divide by zero.");
return 0;
}
return a / b;
};
System.out.println("Division: " + safeDivide.calculate(10, 3));
safeDivide.calculate(10, 0);
// 4. String processing
StringProcessor toUpper = input -> input.toUpperCase();
StringProcessor addPrefix = input -> "[Java] " + input;
System.out.println(toUpper.process("hello"));
System.out.println(addPrefix.process("Lambda Expressions"));
// 5. Using with list sorting
List<String> names = Arrays.asList("Alice", "B", "Charlie", "Da");
names.sort((a, b) -> a.length() - b.length());
System.out.println("By length: " + names);
}
}
Key Functional Interfaces
Core interfaces from the java.util.function package.
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
public class FunctionalInterfaces {
public static void main(String[] args) {
// Function<T, R>: input T -> output R
Function<String, Integer> strLength = String::length;
Function<Integer, String> intToStr = num -> "Number: " + num;
System.out.println("Length: " + strLength.apply("Hello Java")); // 10
System.out.println(intToStr.apply(42));
// Function composition
Function<String, String> toUpper = String::toUpperCase;
Function<String, String> addBrackets = s -> "[" + s + "]";
Function<String, String> combined = toUpper.andThen(addBrackets);
System.out.println(combined.apply("hello")); // [HELLO]
// Predicate<T>: input T -> boolean
Predicate<Integer> isEven = n -> n % 2 == 0;
Predicate<Integer> isPositive = n -> n > 0;
Predicate<String> isNotEmpty = s -> s != null && !s.isEmpty();
System.out.println("Is 4 even? " + isEven.test(4)); // true
System.out.println("Empty string? " + isNotEmpty.test("")); // false
// Combining Predicates
Predicate<Integer> isEvenAndPositive = isEven.and(isPositive);
System.out.println("4: " + isEvenAndPositive.test(4)); // true
System.out.println("-2: " + isEvenAndPositive.test(-2)); // false
// Consumer<T>: input T -> void (no return)
Consumer<String> logger = msg -> System.out.println("[LOG] " + msg);
Consumer<String> alert = msg -> System.out.println("[ALERT] " + msg);
logger.accept("Server started");
logger.andThen(alert).accept("Important event");
// Supplier<T>: no input -> output T
Supplier<Double> randomValue = Math::random;
Supplier<String> greeting = () -> "Hello! Current time: " + System.currentTimeMillis();
System.out.println("Random: " + randomValue.get());
System.out.println(greeting.get());
}
}
Method References
A way to express lambda expressions more concisely.
import java.util.Arrays;
import java.util.List;
import java.util.function.BiFunction;
import java.util.function.Function;
public class MethodReference {
static int add(int a, int b) {
return a + b;
}
static boolean isAdult(int age) {
return age >= 18;
}
public static void main(String[] args) {
List<String> names = Arrays.asList("Java", "Python", "Go", "Rust");
// Lambda vs method reference comparison
// 1. Static method reference (ClassName::methodName)
// Lambda: (a, b) -> Math.max(a, b)
BiFunction<Integer, Integer, Integer> maxFunc = Math::max;
System.out.println("Max: " + maxFunc.apply(10, 20));
BiFunction<Integer, Integer, Integer> addFunc = MethodReference::add;
System.out.println("Sum: " + addFunc.apply(5, 3));
// 2. Instance method reference (instance::methodName)
String prefix = "Hello, ";
Function<String, String> greeter = prefix::concat;
System.out.println(greeter.apply("Java!")); // Hello, Java!
// 3. Arbitrary object instance method reference (ClassName::methodName)
// Lambda: (s) -> s.toUpperCase()
names.stream()
.map(String::toUpperCase) // Calls toUpperCase on each String instance
.forEach(System.out::println);
// 4. Constructor reference (ClassName::new)
Function<String, StringBuilder> sbCreator = StringBuilder::new;
StringBuilder sb = sbCreator.apply("Constructor reference!");
System.out.println(sb);
// Using method reference in sorting
List<String> fruits = Arrays.asList("Banana", "Apple", "Grape", "Strawberry");
fruits.sort(String::compareTo);
System.out.println("Sorted: " + fruits);
}
}
Practical Pattern: Strategy Pattern and Functional Programming
import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
import java.util.function.Predicate;
public class FunctionalPatterns {
// Filtering utility
static <T> List<T> filter(List<T> list, Predicate<T> predicate) {
List<T> result = new ArrayList<>();
for (T item : list) {
if (predicate.test(item)) {
result.add(item);
}
}
return result;
}
// Transformation utility
static <T, R> List<R> map(List<T> list, Function<T, R> mapper) {
List<R> result = new ArrayList<>();
for (T item : list) {
result.add(mapper.apply(item));
}
return result;
}
public static void main(String[] args) {
List<Integer> numbers = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// Inject strategies as lambdas
List<Integer> evenNumbers = filter(numbers, n -> n % 2 == 0);
List<Integer> bigNumbers = filter(numbers, n -> n > 5);
List<String> strings = map(numbers, n -> "Number: " + n);
List<Integer> doubled = map(numbers, n -> n * 2);
System.out.println("Even: " + evenNumbers);
System.out.println("Greater than 5: " + bigNumbers);
System.out.println("Strings: " + strings);
System.out.println("Doubled: " + doubled);
// Function pipeline
Function<Integer, Integer> pipeline = ((Function<Integer, Integer>) (n -> n * 2))
.andThen(n -> n + 10)
.andThen(n -> n * n);
System.out.println("Pipeline(5): " + pipeline.apply(5));
// 5 -> 10 -> 20 -> 400
}
}
Today’s Exercises
-
String Transformer: Create several transformation functions of type
Function<String, String>(trim, uppercase, lowercase, reverse) and chain them withandThento transform the string ” Hello World ” in various ways. -
Filter Chain: Combine
Predicate<Integer>predicates (and, or, negate) to filter numbers from an integer list that are “positive and a multiple of 3” or “even and greater than 10.” -
Simple Event System: Create an
EventBusclass usingMap<String, List<Consumer<String>>>to register and emit handlers by event name. It should supporton("click", handler)andemit("click", data)usage.